package yuhuibear.poat.object.pipe;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Savepoint;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import yuhuibear.poat.object.bean.AttributeMapper;
import yuhuibear.poat.object.bean.AutoValue;
import yuhuibear.poat.object.bean.BeanConstructor;
import yuhuibear.poat.object.bean.BeanStruct;
import yuhuibear.poat.object.bean.PersistentClassLoader;
import yuhuibear.poat.query.BuilderResult;
import yuhuibear.poat.query.ObjectQuery;
import yuhuibear.poat.query.ParamArranger;
import yuhuibear.poat.query.Query;

/**
 * ObjectAccessPipe的具体实现类.l
 * 
 * @author yuhui_bear
 */
public class ObjectAccessPipeImpl extends ObjectAccessPipeBase {

	public ObjectAccessPipeImpl(Connection conn, Integer isolation,
			BeanConstructor bc, PersistentClassLoader structs, ParamArranger pa)
			throws Exception {
		super(conn, isolation, bc, structs, pa);
	}

	@Override
	public <T> List<T> list(Class<T> type, String[] conditions,
			Object... params) throws Exception {
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.SELECT);
		osql.setTarget(type, distinct);
		osql.setConditions(conditions);
		if (pageNumber != null && pageLength != null) {
			osql.setLimit(pageLength);
			osql.setOffset(pageNumber * pageLength);
		}
		String sql = sqlp.getSQL(osql);
		PreparedStatement pst = null;
		Connection con = conn.get();
		try {
			pst = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
										ResultSet.CONCUR_READ_ONLY);
			if (params != null && params.length > 0) {
				pramSetter.setParameters(sql, pst, params);
			}
			ResultSet rs = pst.executeQuery();
			return this.bConstructor.constructList(sql, rs, type);

		} catch (Exception exp) {
			throw new Exception(sql, exp);
		} finally {
			pageLength = null;
			pageNumber = null;
			distinct = true;
		}
	}

	@Override
	public <T> T get(Class<T> type, String[] conditions, Object... params)
			throws Exception {
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.SELECT);
		osql.setTarget(type, distinct);
		osql.setConditions(conditions);
		String sql = sqlp.getSQL(osql);
		PreparedStatement pst = null;
		Connection con = conn.get();
		Savepoint sp = con.setSavepoint();
		try {
			pst = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
										ResultSet.CONCUR_READ_ONLY);
			if (params != null && params.length > 0) {
				pramSetter.setParameters(sql, pst, params);
			}
			ResultSet rs = pst.executeQuery();
			return bConstructor.construct(sql, rs, type);

		} catch (Exception exp) {
			con.rollback(sp);
			throw new Exception(sql, exp);
		} finally {
			con.releaseSavepoint(sp);
		}
	}

	@Override
	public <T> List<T> list(String[] as, Class<T> type, String[] conditions,
			Object... params) throws Exception {
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.SELECT);
		if (pageNumber != null && pageLength != null) {
			osql.setLimit(pageLength);
			osql.setOffset(pageNumber * pageLength);
		}
		osql.setDistinct(distinct);
		String[] iterms = null;
		for (String iterm : as) {
			iterm = iterm.replaceAll(" AS ", " as ");
			iterms = iterm.split("\\sas\\s");
			osql.addAs(iterms[0], iterms[1]);
		}

		osql.setConditions(conditions);
		String sql = sqlp.getSQL(osql);
		PreparedStatement pst = null;
		Connection con = conn.get();
		Savepoint sp = con.setSavepoint();
		try {
			pst = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
										ResultSet.CONCUR_READ_ONLY);
			if (params != null && params.length > 0) {
				pramSetter.setParameters(sql, pst, params);
			}
			ResultSet rs = pst.executeQuery();
			return this.bConstructor.constructList(sql, rs, type);

		} catch (Exception exp) {
			con.rollback(sp);
			throw new Exception(sql, exp);
		} finally {
			con.releaseSavepoint(sp);
			pageLength = null;
			pageNumber = null;
			distinct = true;
		}
	}

	@Override
	public <T> T get(String expression, Class<T> type, String[] conditions,
			Object... params) throws Exception {
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.SELECT);
		osql.setSingleExpression(expression);
		osql.setConditions(conditions);
		String sql = sqlp.getSQL(osql);
		PreparedStatement pst = null;
		Connection con = conn.get();
		Savepoint sp = con.setSavepoint();
		try {
			pst = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
										ResultSet.CONCUR_READ_ONLY);
			if (params != null && params.length > 0) {
				pramSetter.setParameters(sql, pst, params);
			}
			ResultSet rs = pst.executeQuery();
			return this.bConstructor.construct(sql, rs, type);

		} catch (Exception exp) {
			con.rollback(sp);
			throw new Exception(sql, exp);
		} finally {
			con.releaseSavepoint(sp);
		}
	}

	@Override
	public <T> void update(T obj) throws Exception {
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.UPDATE);
		BeanStruct struct = bAnaliser.analyse(obj.getClass());
		osql.setTarget(obj.getClass());
		String sql = sqlp.getSQL(osql);
		PreparedStatement pst = null;
		Connection con = conn.get();
		Savepoint sp = con.setSavepoint();
		try {
			pst = con.prepareStatement(sql);
			pramSetter.setParametersAnalysed(sql, pst, obj,
												struct.getPrimaryKeyValue(obj));
			pst.executeUpdate();

		} catch (Exception exp) {
			con.rollback(sp);
			throw new Exception(sql, exp);
		} finally {
			con.releaseSavepoint(sp);
		}

	}

	@Override
	public int update(Class<?> type, String[] attrs, String[] conditions,
			Object... params) throws Exception {
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.UPDATE);
		osql.setTarget(type);
		for (int i = 0; i < attrs.length; i++) {
			osql.setUpdateAttributes(attrs[i]);
		}
		osql.setConditions(conditions);
		String sql = sqlp.getSQL(osql);
		PreparedStatement pst = null;
		int cnt = 0;
		Connection con = conn.get();
		Savepoint sp = con.setSavepoint();
		try {
			pst = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
										ResultSet.CONCUR_READ_ONLY);
			pramSetter.setParameters(sql, pst, params);
			cnt = pst.executeUpdate();
		} catch (Exception exp) {
			con.rollback(sp);
			if (conditions == null) {
				sql = sql
						+ "\n !!! if you want to update all records of table, please use \"*\" instead of null";
			}
			throw new Exception(sql, exp);
		} finally {
			con.releaseSavepoint(sp);
		}
		return cnt;
	}

	@Override
	public <T> void delete(T obj) throws Exception {
		BeanStruct struct = bAnaliser.analyse(obj.getClass());
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.DELETE);
		osql.setTarget(obj.getClass());
		String sql = sqlp.getSQL(osql);
		PreparedStatement pst = null;
		Connection con = conn.get();
		Savepoint sp = con.setSavepoint();
		try {
			pst = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
										ResultSet.CONCUR_READ_ONLY);
			String pk = struct.getPrimaryKeyName();
			if (pk != null && pk.length() > 0) {
				pramSetter.setParametersAnalysed(
													sql,
													pst,
													struct.getPrimaryKeyValue(obj));
			}
			else {
				pramSetter.setParametersAnalysed(sql, pst, obj);
			}
			pst.executeUpdate();
		} catch (Exception exp) {
			con.rollback(sp);
			throw new Exception(sql, exp);
		} finally {
			con.releaseSavepoint(sp);
		}
	}

	@Override
	public <T> int delete(List<T> lst) throws Exception {
		if (lst.size() < 1) { throw new Exception("cann't add null !!! "); }
		BeanStruct struct = bAnaliser.analyse(lst.get(0).getClass());
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.DELETE);
		osql.setTarget(lst.get(0).getClass());
		String sql = sqlp.getSQL(osql);
		PreparedStatement pst = null;
		int cnt = 0;
		Connection con = conn.get();
		Savepoint sp = con.setSavepoint();
		try {
			Iterator<?> itr = lst.iterator();
			pst = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
										ResultSet.CONCUR_READ_ONLY);
			String pk = struct.getPrimaryKeyName();
			if (pk != null && pk.length() > 0) {
				while (itr.hasNext()) {
					pramSetter.setParametersAnalysed(
														sql,
														pst,
														struct.getPrimaryKeyValue(itr.next()));
					pst.executeUpdate();
					cnt++;
				}
			}
			else {
				while (itr.hasNext()) {
					pramSetter.setParametersAnalysed(sql, pst, itr.next());
					pst.executeUpdate();
					cnt++;
				}
			}

		} catch (Exception exp) {
			con.rollback(sp);
			throw new Exception(sql, exp);
		} finally {
			con.releaseSavepoint(sp);
		}
		return cnt;
	}

	@Override
	public <T> void add(T obj) throws Exception {
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.INSERT);
		osql.setTarget(obj.getClass());
		BuilderResult build = sqlp.getSQL(osql, obj);
		PreparedStatement pst = null;
		Connection con = conn.get();
		Savepoint sp = con.setSavepoint();
		try {
			pst = con.prepareStatement(build.sql, ResultSet.TYPE_FORWARD_ONLY,
										ResultSet.CONCUR_READ_ONLY);
			pramSetter.setParametersWithNames(build, pst, obj);
			pst.executeUpdate();
			BeanStruct bean = bAnaliser.analyse(obj.getClass());
			Map<String, AutoValue> autos = bean.getAutoValueStatements();
			if (autos != null) {
				for (Map.Entry<String, AutoValue> entry : autos.entrySet()) {
					AutoValue av = entry.getValue();
					// 越过已有值
					if (av.isSkipOnNull()
							&& bean.getValue(entry.getKey(), obj) == null) {
						pst = con.prepareStatement(av.getSql());
						AttributeMapper mapper = bean.getAttribute(entry.getKey());
						Object aValue = bConstructor.construct(
																av.getSql(),
																pst.executeQuery(),
																mapper.getAttributeType());
						mapper.setValue(obj, aValue);
					}

				}
			}
		} catch (Exception exp) {
			con.rollback(sp);
			throw new Exception(build.sql, exp);
		} finally {
			con.releaseSavepoint(sp);
		}
	}

	@Override
	public <T> int add(List<T> objs) throws Exception {
		if (objs.size() < 1) { throw new Exception("no elements to add"); }
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.INSERT);
		osql.setTarget(objs.get(0).getClass());
		BuilderResult build = new BuilderResult();
		PreparedStatement pst = null;
		int cnt = 0;
		Connection con = conn.get();
		Savepoint sp = con.setSavepoint();
		try {
			BeanStruct bean = bAnaliser.analyse(objs.get(0).getClass());
			Map<String, AutoValue> autos = bean.getAutoValueStatements();

			for (Object obj : objs) {
				build = sqlp.getSQL(osql, obj);
				pst = con.prepareStatement(build.sql,
											ResultSet.TYPE_FORWARD_ONLY,
											ResultSet.CONCUR_READ_ONLY);
				pramSetter.setParametersWithNames(build, pst, obj);
				pst.executeUpdate();

				if (autos != null) {
					for (Map.Entry<String, AutoValue> entry : autos.entrySet()) {
						AutoValue av = entry.getValue();
						if (av.isSkipOnNull()
								&& bean.getValue(entry.getKey(), obj) != null) {
							continue;
						}
						pst = con.prepareStatement(av.getSql());
						AttributeMapper mapper = bean.getAttribute(entry.getKey());
						Object aValue = bConstructor.construct(
																av.getSql(),
																pst.executeQuery(),
																mapper.getAttributeType());
						mapper.setValue(obj, aValue);
					}
				}
				cnt++;
			}
		} catch (Exception exp) {
			con.rollback(sp);
			throw new Exception(build.sql, exp);
		} finally {
			con.releaseSavepoint(sp);
		}
		return cnt;
	}

	@Override
	public <T> int delete(Class<T> type, String[] conditions, Object... params)
			throws Exception {
		ObjectQuery osql = new ObjectQuery(this.bAnaliser, Query.DELETE);
		osql.setTarget(type);
		osql.setConditions(conditions);
		String sql = sqlp.getSQL(osql);
		PreparedStatement pst = null;
		int cnt = 0;
		Connection con = conn.get();
		Savepoint sp = con.setSavepoint();
		try {
			pst = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
										ResultSet.CONCUR_READ_ONLY);
			pramSetter.setParameters(sql, pst, params);
			cnt = pst.executeUpdate();
		} catch (Exception exp) {
			con.rollback(sp);
			if (conditions == null) {
				sql = sql
						+ "\n !!! if you want to delete all records of table, please use \"*\" instead of null";
			}
			throw new Exception(sql, exp);
		} finally {
			con.releaseSavepoint(sp);
		}
		return cnt;
	}

}
